﻿/* Copyright 2012 Sannel Software, L.L.C.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Sannel.Calculate.Nodes;
using Sannel.Calculate.Nodes.Numbers;

namespace Sannel.Calculate
{
	public class Parser
	{
		public void Parse(String source)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}

			if (StringHelpers.IsNullOrWhitespace(source))
			{
				throw new ArgumentException(Messages.CannotParseEmptyString, "source");
			}
		}

		protected BaseNode FirstNode
		{
			get;
			set;
		}

		protected BaseNode CurrentNode
		{
			get;
			set;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
		protected static BaseNode CreateTypeFromString(String nodeValue)
		{
			if (nodeValue == null)
			{
				throw new ArgumentNullException("nodeValue");
			}

			if (StringHelpers.IsNullOrWhitespace(nodeValue))
			{
				throw new ArgumentException(Messages.NodeValueMustNotBeNullOrEmpty);
			}

			nodeValue = nodeValue.Trim();
			switch (nodeValue)
			{
				case "+":
					return new OperatorNode(OperatorCategory.Additive, OperatorType.Plus);
				case "-":
					return new OperatorNode(OperatorCategory.Additive, OperatorType.Minus);

				case "*":
					return new OperatorNode(OperatorCategory.Multiplicative, OperatorType.Multiply);

				case "/":
					return new OperatorNode(OperatorCategory.Multiplicative, OperatorType.Division);

				case "%":
					return new OperatorNode(OperatorCategory.Multiplicative, OperatorType.Modules);

				case "++":
					return new OperatorNode(OperatorCategory.Primary, OperatorType.Increment);

				case "--":
					return new OperatorNode(OperatorCategory.Primary, OperatorType.Decrement);

				default:
					break;
			}

			var firstChar = nodeValue[0];

			if ((firstChar >= '0' && firstChar <= '9') || firstChar == '.') // number
			{
				if (nodeValue.IndexOf('.') > -1)
				{
					double value = 0;
					if (Double.TryParse(nodeValue, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out value)) // intresting this only goes to the 12th decimal place
					// TODO: create own parser that will read all precision for double
					{
						return new DoubleNode(value);
					}
					else
					{
						throw new ExpressionParseException(Messages.InvalidDouble, nodeValue);
					}
				}
				else
				{
					int value = 0;
					long lvalue = 0;
					if (Int32.TryParse(nodeValue, out value))
					{
						return new Int32Node(value);
					}
					else if (Int64.TryParse(nodeValue, out lvalue))
					{
						return new Int64Node(lvalue);
					}
					else
					{
						throw new ExpressionParseException(Messages.InvalidInt32OrInt64, nodeValue);
					}
				}
			}
			else // variable
			{
				StringBuilder name = new StringBuilder();
				String variableName = nodeValue.Trim();
				char c;

				for (int i = 0; i < variableName.Length; i++)
				{
					c = variableName[i];

					if (c == ' ' || c == '+' || c == '-' || c == '/' || c == '\\' || c == '%' || c == '*' || c == '{' || c == '}' || c == '(' || c == ')')
					{
						throw new ExpressionParseException(Messages.NotValidInAVariable, c, variableName);
					}
					else
					{
						name.Append(c);
					}
				}

				return new VariableNode(name.ToString());
			}
		}

		/// <summary>
		/// Appends the node.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <exception cref="System.ArgumentNullException"></exception>
		protected void AppendNode(BaseNode node)
		{
			if (node == null)
			{
				throw new ArgumentNullException("node");
			}

			if (FirstNode == null)
			{
				FirstNode = node;
				CurrentNode = FirstNode;
			}
			else
			{
				CurrentNode.InsertNext(node);
				CurrentNode = node;
			}
		}

		protected void FlushAndClear(StringBuilder builder)
		{
			if (builder == null)
			{
				throw new ArgumentNullException("builder");
			}

			if (builder.Length > 0)
			{
				AppendNode(CreateTypeFromString(builder.ToString()));
				builder.Remove(0, builder.Length);
			}
		}

		protected void CreateNodesFromLine(String line)
		{
			if (line == null)
			{
				throw new ArgumentNullException("line");
			}

			StringBuilder builder = new StringBuilder();

			char c;
			for (int i = 0; i < line.Length; i++)
			{
				c = line[i];

				if (c == ' ')
				{
					FlushAndClear(builder);
				}
				else if (c == '-')
				{
					if (i + 1 < line.Length && line[i + 1] == '-')
					{
						FlushAndClear(builder);
						AppendNode(CreateTypeFromString("--"));
						i++;
					}
					else
					{
						FlushAndClear(builder);
						AppendNode(CreateTypeFromString("-"));
					}
				}
				else if (c == '+')
				{
					if (i + 1 < line.Length && line[i + 1] == '+')
					{
						FlushAndClear(builder);
						AppendNode(CreateTypeFromString("++"));
						i++;
					}
					else
					{
						FlushAndClear(builder);
						AppendNode(CreateTypeFromString("+"));
					}
				}
				else if (c == '/' || c == '*' || c == '%')
				{
					FlushAndClear(builder);
					AppendNode(CreateTypeFromString(c.ToString()));
				}
				else
				{
					builder.Append(c);
				}
			}

			if (builder.Length > 0)
			{
				AppendNode(CreateTypeFromString(builder.ToString()));
				builder.Remove(0, builder.Length);
			}
		}
	}
}
